import { _decorator, clamp, Component, game, instantiate, isValid, JsonAsset, Node, Prefab, RigidBody, Vec3 } from 'cc';
import { eliminateGameManager } from './EliminateGameManager';
import NDB from '../../common/NDBTS';
import { resourceManager } from '../../common/ResourceManager';
import { GameConfig } from '../../GameConfig';
import { EliminateToy } from './EliminateToy';
import { createRandom, getUrlParameterValue, shuffle } from '../../common/util';
import { eliminateConsts, EliminateMode } from './EliminateConst';
import { profiler } from 'cc';
import { tween } from 'cc';
import { ParticleSystem2D } from 'cc';
import NetworkManager from '../../common/NetworkManager';
import NetworkMessage from '../../common/NetworkMessage';
import { eventManager } from '../../common/eventManager';
import { EliminateProgress } from './EliminateProgress';
import { socketManager } from '../../common/SocketManager';
import { userManager } from '../../common/UserManager';
import { EliminateMatchMessage, EliminateMessage, EliminateOver, GameStopReason, JoinResponse, StartRequest, StartResponse } from './EliminateRequest';
import { macro } from 'cc';
import { CCBoolean } from 'cc';
import nsnoSleep from '../../common/nosleep';
import { audioManager } from '../../common/AudioManager';
import CommonToastManager from '../../common/Toast/CommonToastManager';
import { GaussianBlur } from './postProcess/GaussianBlur';
import { v3 } from 'cc';
import { view } from 'cc';
import { geometry } from 'cc';
import { EliminateUIManager } from './EliminateUIManager';
import { PhysicsSystem } from 'cc';
import { Size } from 'cc';
const { ccclass, property } = _decorator;

export interface ToyData {
    name: string,
    scale: number,
    id: number
}

export enum GameOverReason {
    NONE,
    /** 正常结束 */
    NORMAL_OVER,
    /** 倒计时结束 */
    COUNTDOWN_OVER,
    /** 没有完成关卡 */
    NOT_COMPLETE_LEVEL,
    /** 用户主动离开 */
    LEAVE
}

@ccclass('EliminateGame')
export class EliminateGame extends Component {

    public overReason: GameOverReason = GameOverReason.NONE;

    @property(Node)
    plane: Node = null;

    @property(Node)
    bornNode: Node = null;

    @property(CCBoolean)
    isDebug: boolean = true;

    @property(Prefab)
    toastPrefab: Prefab = null;

    @property(Node)
    boards: Node = null;

    @property(Node)
    cubeNode: Node = null;

    @property(JsonAsset)
    typeConfig: JsonAsset = null;

    @property(JsonAsset)
    allLevels: JsonAsset = null;

    @property(ParticleSystem2D)
    clearPart: ParticleSystem2D = null;

    @property(EliminateProgress)
    eliminateProgress: EliminateProgress = null;

    @property(EliminateProgress)
    match3EliminateProgress: EliminateProgress = null;

    @property(GaussianBlur)
    gaussianBlur: GaussianBlur = null;

    /** 是否正在消除 */
    public isEliminating: boolean = false;

    /** 游戏的所有玩家 */
    // public players: Map<string, Player> = new Map();
    public self: string = '';

    /** 在20秒内 如果又消除了一对的话 倍数加1 */
    private _times: number = 1;

    private _minX: number = 0;
    private _maxX: number = 0;
    private _minZ: number = 0;
    private _maxZ: number = 0;

    public get minX() {
        return this._minX;
    }

    public get maxX() {
        return this._maxX;
    }

    public get minZ() {
        return this._minZ;
    }

    public get maxZ() {
        return this._maxZ;
    }

    public static hasJoin: boolean = false;

    public set times(v: number) {
        eliminateGameManager.uiManager.updateTimesLabel(v);
        this._times = v;
    }

    public propCanUse: boolean = false;

    public get times() {
        return this._times;
    }

    private _score: number = 0;
    public set score(v: number) {
        eliminateGameManager.effectManager.generateStarToScore(v);
        eliminateGameManager.uiManager.updateScore(v);
        eliminateGameManager.uiManager.updateToolCountLabel(Math.floor(v / eliminateConsts.auto_cost_score), this.propCanUse);
        this._score = v;
    }
    public get score() {
        return this._score;
    }

    private nodes: Node[] = [];

    public getNodes() {
        return this.nodes;
    }
    private nodeMap: Map<string, Node> = new Map();
    /** 节点是否被锁定 */
    private nodeLockMap: Map<string, boolean> = new Map();
    /** 用户消除槽 */
    // public usersSlot: Map<string, SlotTrigger> = new Map();

    private toyConfig: ToyData[] = null;

    /** 是否正在同步socket数据 */
    private _isSyncData: boolean = false;

    public get isSyncData() {
        return this._isSyncData;
    }

    public set isSyncData(v: boolean) {
        this._isSyncData = v;
    }

    private thisRoundWinScore: number = 0;

    /** 10ms同步一次数据 */
    private _syncStepTime: number = 0.01;

    /** 0.5秒同步一次所有物体的坐标，旋转信息 */
    private _updateSyncStep: number = 0.5;
    private _updateDelta: number = 0;
    private downY: number = 300;

    public gameOver: boolean = false;

    protected __preload(): void {
        eliminateGameManager.game = this;
        let appName = getUrlParameterValue("app");
        console.log("appName is ", appName);

        profiler.hideStats();
    }

    protected onLoad(): void {
        NDB.gameIsStart = true;
        // assetManager.preloadAny()
        macro.ENABLE_MULTI_TOUCH = false;
        NetworkManager.Init();
        NetworkManager.getUserInfo().then((res: { avatar: string, username: string, userId: string }) => {
            userManager.userId = res.userId;
            userManager.username = res.username;
            userManager.avatar = res.avatar;
        });

        this.joinGame().then(() => {
            this.startGame();
        });
        console.log("curLevel is ", eliminateGameManager.curLevel);
        this.toyConfig = this.typeConfig.json as ToyData[];
    }

    isEnableSlotsNode() {
        this.scheduleOnce(() => {
            eliminateGameManager.uiManager.slotsNode.node.active = eliminateGameManager.gameMode == EliminateMode.MATCH2;
            eliminateGameManager.uiManager.slotNode.active = eliminateGameManager.gameMode == EliminateMode.MATCH2;
        }, 2);
    }

    tapNode() {
        const ns = new nsnoSleep();
        ns.enable();
    }

    public async joinGame() {
        return new Promise((resolve, reject) => {
            NetworkManager.httpResult("post", NetworkMessage.ELIMINATE3D_JOIN, {}, (res: { data: JoinResponse }) => {
                console.log("join res is ", res);
                eliminateGameManager.curLevel = res.data.current_max_level;
                eliminateGameManager.curPlayLevel = res.data.current_max_level;
                eliminateGameManager.game.score = res.data.current_score;
                resolve(null);
            });
        })
    }


    public leaveGame() {
        return new Promise((resolve, reject) => {
            NetworkManager.httpResult("post", NetworkMessage.ELIMINATE3D_LEAVE, {}, (res: any) => {
                console.log("leave res is ", res);
                const overData: EliminateOver = {
                    type: 'over',
                    user_id: +userManager.userId,
                    game_id: eliminateGameManager.gameId,
                    level: eliminateGameManager.curPlayLevel,
                    stop_reason: GameStopReason.FORCE_CLOSE
                }
                // 发送游戏结束消息
                socketManager.sendMessage(overData);
                resolve(null);
            });
        });
    }

    public startGame() {
        return new Promise((resolve, reject) => {
            const startData: StartRequest = {
                level: eliminateGameManager.curPlayLevel
            }
            console.log("startData is ", startData);
            NetworkManager.httpResult("post", NetworkMessage.ELIMINATE3D_START, startData, (response: { data: StartResponse }) => {
                console.log("response is ", response);

                if (!response['code'] && response['data']) {
                    eliminateGameManager.gameId = response.data.game_id;
                    const entitys = response.data.entity_ids;

                    eliminateGameManager.uiManager.updateLevelNumberLabel(eliminateGameManager.curPlayLevel);

                    eliminateGameManager.curLevel = response.data.current_max_level;
                    this.propCanUse = response.data.prop_usable;
                    eliminateGameManager.uiManager.updateToolCountLabel(response.data.free_props["1"], response.data.prop_usable);
                    const cd = response.data.countdown;
                    eliminateGameManager.uiManager.topUI.totalTime = cd;
                    eliminateGameManager.uiManager.topUI.startCountdown = true;

                    this.initAllBox(entitys, this.isEnableSlotsNode.bind(this));
                    this.score = response.data.current_score;
                    socketManager.initGameServer('match3d', response.data.game_id);
                    socketManager.initSocket();
                    resolve(null);
                } else {
                    console.log("start response is error!");
                    CommonToastManager.toast(0.8, "Network error", this.toastPrefab);
                }
            });
        });
    }

    _getUserInfo() {
        NDB.getUserInfo().then((userInfo: any) => {
            console.log("userInfo si ", userInfo);
        });
    }

    public async initAllBox(idArray: number[], callback?: Function) {
        let boxRoot = this.bornNode.getChildByName("all");

        const offset = 100;

        let xMin = this._minX + offset;
        let xMax = this._maxX - offset;
        let zMin = this._minZ - offset;
        let zMax = this._maxZ + offset;

        let modelNames = idArray;
        const newArr: { id: number, name: string }[] = [];
        let scales = [];
        let ids = [];

        const targetLen = 3;
        newArr.length = modelNames.length * targetLen;

        let index = 0;
        for (let i = 0; i < modelNames.length; i++) {
            for (let j = 0; j < targetLen; j++) {
                ids[index] = modelNames[i];
                let obj = {
                    id: modelNames[i],
                    name: this.typeConfig.json[modelNames[i]]
                }
                newArr[index] = obj;
                index++;
            }
        }

        shuffle(newArr);
        console.log("newArr is ", newArr);
        // 分层下落 一层下落50个
        let layer = 0;
        let y = this.downY;

        const gridRow: number = 6, gridCol: number = 6;

        const generateGrid = (row: number, col: number) => {
            let poses = [];
            const gridWidth = (xMax - xMin) / col;
            const gridHeight = (zMin - zMax) / row;

            let startX = xMin + gridWidth / 2;
            let startZ = zMax + gridHeight / 2;

            for (let i = 0; i < row; i++) {
                for (let j = 0; j < col; j++) {
                    let posItem = [];
                    posItem[0] = startX + j * gridWidth;
                    posItem[1] = startZ + i * gridHeight;
                    poses.push(posItem);
                }
            }
            return poses;
        }

        // 一层有36个位置
        let layPoses = generateGrid(gridRow, gridCol);

        for (let i = 0; i < newArr.length; i++) {
            const randomX = layPoses[i % (gridRow * gridCol)][0];
            const randomZ = layPoses[i % (gridRow * gridCol)][1];

            if (i > 0 && i % (gridRow * gridCol) == 0) {
                layer++;
            }
            y += layer * 10;

            // 每一层分网格下落

            new Promise((resolve, reject) => {
                resourceManager.loadAssetInBundle(`prefabs/${newArr[i].name}`, Prefab, GameConfig.gameName).then((res: Prefab) => {
                    if (res) {
                        const node = instantiate(res);
                        if (!node.getComponent(EliminateToy)) {
                            node.addComponent(EliminateToy);
                        }
                        const rb = node.getComponent(RigidBody);
                        // rb && (rb.useCCD = true);
                        boxRoot.addChild(node);

                        node.name = `${newArr[i].name}${i}`;
                        node['tag'] = `${newArr[i].name}`;
                        node['myType'] = "toy";
                        node['clear'] = false;
                        node['inSlot'] = false;

                        const toyComp = node.getComponent(EliminateToy);
                        toyComp.toyId = newArr[i].id;

                        node.setPosition(randomX, y, randomZ);
                        node.getWorldPosition();

                        this.nodes.push(node);
                        this.nodeMap.set(node.name, node);
                        // this.lockNodeByName(node.name, false);
                        if (i == newArr.length - 1) {
                            callback && callback();
                        }
                    }
                    resolve(null);
                });
            });
        }

        // this.checkHasPair();
        if (game.renderType == 3) {
            // window['nodeMap'] = this.nodeMap;
            // gameManager.networkManager.room && gameManager.networkManager.room.send("born_box");
        }
    }

    public findAtoyFromHeap(nodes: Node[][]) {
        let allNodes = [];
        this.nodeMap.forEach((node: Node) => {
            if (node && !node['clear']) {
                allNodes.push(node);
            }
        });

        if (allNodes.length <= 0) return [];
        // 消除队列里面的东西都成对了
        const allIsPair = nodes.every(item => item.length == eliminateConsts.eliminateLimit);
        if (nodes.length == 0 || allIsPair) {
            // 查看消除槽子里面有没有带消除的物体
            const node1 = allNodes[0];
            const tag = node1['tag'];
            const node2 = allNodes.find(item => item['tag'] == tag && item != node1);
            return [node1, node2];
        } else {
            // 最后一个待消除列表
            const lastNodes = nodes[nodes.length - 1];
            if (lastNodes.length >= 1 && lastNodes.length < eliminateConsts.eliminateLimit) {
                const tagNode = lastNodes[lastNodes.length - 1];
                const tag = tagNode['tag'];
                const targetNode = allNodes.find(it => it['tag'] == tag && it != tagNode);
                return [targetNode];
            }
            return [];
        }
    }

    public deleteNodeFromeNodes(node: Node) {
        let targetIndex = this.nodes.indexOf(node);
        this.nodes.splice(targetIndex, 1);
        if (this.nodes.length == 0) {
            this.overReason = GameOverReason.NORMAL_OVER;
        }
    }


    public checkHasPair(): boolean {
        let pairMap = new Map();
        this.nodeMap.forEach((value, item) => {
            if (!isValid(value, true)) return;
            const toy = value.getComponent(EliminateToy);
            if (!pairMap.get(toy.toyId)) {
                pairMap.set(toy.toyId, 1);
            } else {
                let v = pairMap.get(toy.toyId);
                pairMap.set(toy.toyId, v + 1);
            }
        })
        console.log("paieMap is ", pairMap);
        let pair = true;

        if (pairMap.size == 0) {
            this.overReason = GameOverReason.NORMAL_OVER;
            const overData: EliminateOver = {
                type: 'over',
                user_id: +userManager.userId,
                game_id: eliminateGameManager.gameId,
                level: eliminateGameManager.curPlayLevel,
                stop_reason: GameStopReason.CLEAR
            }
            // 发送游戏结束消息
            socketManager.sendMessage(overData);
        }

        pairMap.forEach((value, key) => {
            if (value % 2 != 0) {
                console.log(`${key}not pair`);
                pair = false;
                return pair;
            }
        });
        return pair;
    }

    start() {
        this.scheduleOnce(() => {

            const visibleSize = view.getVisibleSize();
            const visibleSizeInPixel = view.getVisibleSizeInPixel();

            console.log("visibleSize is ", visibleSize);
            console.log("visibleInPixel size is ", visibleSizeInPixel);

            this._findBoardCoord();
            // 等待下一帧才能取到边界
            console.log(eliminateGameManager.uiManager.camera.orthoHeight);
        }, 0);

    }

    /**
     * 
     * 寻找3d世界的坐标
     * 
     */
    private _findBoardCoord() {
        // if (eliminateGameManager.gameMode == EliminateMode.MATCH2) return;
        const d3Camera = eliminateGameManager.uiManager.camera;

        // const canvasSize = new Size(Math.max(view.getVisibleSizeInPixel().width, view.getVisibleSize().width), Math.max(view.getVisibleSizeInPixel().height, view.getVisibleSize().height));
        const canvasSize = view.getVisibleSize();
        console.log("canvansSzie is ", canvasSize);
        // d3Camera.camera.setFixedSize(canvasSize.width, canvasSize.height);
        d3Camera.camera.orthoHeight = canvasSize.height / 2;
        eliminateGameManager.uiManager.toastCamera.orthoHeight = canvasSize.height / 2;
        d3Camera.camera.update(true);



        const wratio = canvasSize.width / view.getDesignResolutionSize().width;
        // eliminateGameManager.uiManager.topUI.adapterSelf(canvasSize.height / 2 - 75);

        this.scheduleOnce(() => {
            let leftUI = v3(0, canvasSize.height / 2, 0);
            let rightUI = v3(canvasSize.width, canvasSize.height / 2, 0);
            let topUI = v3(canvasSize.width / 2, canvasSize.height - 100 * wratio, 0);
            let bottomUI = v3(canvasSize.width / 2, 200 * wratio, 0);

            let left3d = d3Camera.screenToWorld(leftUI);
            let right3d = d3Camera.screenToWorld(rightUI);
            let top3d = d3Camera.screenToWorld(topUI);
            let bottom3d = d3Camera.screenToWorld(bottomUI);

            this._minX = left3d.x;
            this._maxX = -left3d.x;
            // 底部的
            this._minZ = bottom3d.z - 150 * wratio;
            // 顶部
            this._maxZ = -bottom3d.z + 20 * wratio;

            const leftBoard = this.boards.getChildByName("leftBoard");
            const rightBoard = this.boards.getChildByName("rightBoard");
            const topBoard = this.boards.getChildByName("topBoard");

            const leftBoardV = this.boards.getChildByName("leftBoardV");
            const rightBoardV = this.boards.getChildByName("rightBoardV");
            const topBoardV = this.boards.getChildByName("topBoardV");
            const bottomBoardV = this.boards.getChildByName("bottomBoardV");

            leftBoard.setPosition(this._minX, 0, 0);
            rightBoard.setPosition(this._maxX, 0, 0);
            topBoard.setPosition(0, 0, this._maxZ);

            leftBoardV.setPosition(this._minX, 200, 0);
            rightBoardV.setPosition(this._maxX, 200, 0);
            topBoardV.setPosition(0, 200, this._maxZ);
            bottomBoardV.setPosition(0, 200, this._minZ);

            if (this.isDebug) {
                eliminateGameManager.uiManager.topUI.totalTime = 1000;
                eliminateGameManager.uiManager.topUI.startCountdown = true;

                const allLevels = this.allLevels.json;
                const curPlayLevel = clamp(eliminateGameManager.curPlayLevel - 1, 0, this.allLevels.json.length);
                const entitys = [];

                const element = allLevels[curPlayLevel];
                element['objects'].forEach(ele => {
                    let pair = Math.floor(ele["count"] / eliminateConsts.eliminateLimit);
                    for (let i = 0; i < pair; i++) {
                        entitys.push(ele['id']);
                    }
                });
                this.initAllBox(entitys, this.isEnableSlotsNode.bind(this));
            }
        }, 0);

    }

    protected onEnable(): void {
        eventManager.on('emit_message', this._socketHandler, this);
        eventManager.on('androidGoback', this._androidGoback, this);
    }

    public _socketHandler(message: any) {
        console.log("message is ", message);
        const messageData = message.data as EliminateMatchMessage;
        const method = message.method;
        switch (method) {
            case "match":
                const curScore = messageData.current_score;
                if (messageData.free_props) {
                    // eliminateGameManager.uiManager.updateToolCountLabel(messageData.free_props["1"]);
                }
                this.score = curScore;
                break;
            case "over":
                console.log("over messageData is ", messageData, " over time is ", Date.now());
                this.score = messageData['current_score'];
                const curLevel = messageData['current_max_level'];
                eliminateGameManager.curLevel = curLevel;
                this.gameOver = true;
                const isWin = this.overReason == GameOverReason.NORMAL_OVER ? true : false;
                eliminateGameManager.uiManager.showResult(isWin, this.thisRoundWinScore);
                break;
        }
    }

    protected onDisable(): void {
        eventManager.remove('emit_message', this._socketHandler);
        eventManager.remove('androidGoback', this._androidGoback);
        // this.node.off(Node.EventType.TOUCH_END, this.tapNode, this);

    }

    _androidGoback() {
        eliminateGameManager.uiManager.popPause();
    }

    public async eliminate(nodes: Node[]) {
        this.isEliminating = true;
        let self = this;
        return new Promise((resolve, reject) => {
            const toys = nodes;
            if (toys.length >= 1 && toys.length < eliminateConsts.eliminateLimit) {
                debugger;
            }
            this.clearPart.resetSystem();


            let centerPos = new Vec3(0, 6, eliminateGameManager.uiManager.slotsNode.leftPos.z);
            let downPos = new Vec3(0, -10, eliminateGameManager.uiManager.slotsNode.leftPos.z);

            eventManager.emit(eliminateConsts.messages.ELIMINATE);
            const score = this.addScore();

            // 所有的玩具向中间移动，然后合并成一个落下
            toys.forEach((item, index) => {
                // 标记该节点为销毁状态
                item['clear'] = true;
                console.log("clear name is ", item.name);
                eliminateGameManager.uiManager.slotsNode.removeFromBoxsOrWaitEnterNodes(item);
                if (index == 0) {
                    const eliminateData: EliminateMessage = {
                        game_id: eliminateGameManager.gameId,
                        type: "match",
                        entity_id: item.getComponent(EliminateToy).toyId,
                        user_id: +userManager.userId,
                        prop_id: eliminateGameManager.useTool ? 1 : 0,
                        win_score: score
                    }
                    socketManager.sendMessage(eliminateData);
                }

                tween(item).to(0.05, { worldPosition: centerPos }, { easing: 'cubicInOut' }).call(() => {
                    eliminateGameManager.useTool = false;
                    tween(item).to(0.1, { worldPosition: downPos, scale: new Vec3(0.5, 0.5, 0.5) }).call(() => {
                        const targetIndex = this.nodes.indexOf(item);
                        // this.nodes.splice(targetIndex, 1);
                        this.nodeMap.set(item.name, null);

                        if (index == 0) {
                            this.scheduleOnce(() => {
                                resolve(null);
                            }, 0);
                        }
                        item.destroy();
                    }).start();
                }).start();
            });
        });
    }

    public addScore(): number {
        const score = eliminateConsts.eliminate_win_score * this.times;
        this.thisRoundWinScore += score;
        this.score += score;
        if (eliminateGameManager.gameMode == EliminateMode.MATCH2) {
            this.eliminateProgress.startCountdown = true;
        } else {
            this.match3EliminateProgress.startCountdown = true;
        }
        audioManager.playEffect('audio/goal');
        return score;
    }

    /**
     * 找出三个相同的物体返回
     * @returns Node[]
     */
    public getRandomThreeCommonTypeToy(type?: string): Node[] {
        let nodes = [];

        const noClearNodes = this.nodes.filter(item => {
            let valid = isValid(item, true);
            if (!valid) return false;
            let clearTag = !item['clear'];
            if (!clearTag) return false;
            let isInSlot = item.getComponent(EliminateToy).slotIndex != -1;
            if (isInSlot) return false;
            // 合法的节点 && 没有被标记为消除状态的节点 && 没有在消除槽子里的
            return valid && clearTag && !isInSlot;
        });

        let len = noClearNodes.length;
        let index = createRandom(0, len);

        let name = "";

        if (type) {
            name = type;
        } else {
            // 随机从堆里面找到一个类型的物体
            name = noClearNodes[index]['tag'];
        }

        // let randomTag = thi
        for (let i = 0; i < len; i++) {
            let item = noClearNodes[i];
            if (!item) continue;
            if (item['tag'] == name) {
                nodes.push(item);
            }
        }
        return nodes;
    }

    protected update(dt: number): void {
        // this._findBoardCoord();
    }
}

